﻿#include <WS2tcpip.h>
#include <tchar.h>
#include "Server.h"

// 函数声明
static void Server_Delete(Server*);
static void Server_HandleFindUsers(Server*, GomokuProtocol*, SOCKET);
static void Server_HandleLogon(Server*, GomokuProtocol*, SOCKET);
static void Server_HandleProtocol(Server*, GomokuProtocol*);
static void Server_HandleQuitGame(Server*, GomokuProtocol*, SOCKET);
static void Server_OnGetMessageFromClient(Server*, GomokuProtocol*, SOCKET);
static DWORD WINAPI Server_Thread(LPVOID);
static void Server_RemoveUser(Server*, SOCKET);
static void Server_SendData(SOCKET, const void*, int);
static BOOL Server_Start(Server*);

/**
 * 创建服务器。
 * @return 服务器
 */
Server* New_Server() {
    Server* server = (Server*)calloc(1, sizeof(Server));
    WSADATA wsaData = { 0 };

    // 初始化网络
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // 初始化数据
    server->mOnlineUsers = New_OnlineUserList();
    server->mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // 初始化函数
    server->deleteSelf = Server_Delete;
    server->start = Server_Start;

    return server;
}

/**
 * 删除服务器。
 * @param server    服务器
 */
static void Server_Delete(Server* server) {
    // 关闭套接字
    closesocket(server->mSocket);

    // 清空在线用户列表
    server->mOnlineUsers->removeAll(server->mOnlineUsers);
    server->mOnlineUsers->deleteSelf(server->mOnlineUsers);

    // 反初始化网络
    WSACleanup();
}

/**
 * 处理寻找其它用户请求。
 * @param server    服务器
 * @param request   请求
 * @param client    客户端
 */
static void Server_HandleFindUsers(Server* server, GomokuProtocol* request, SOCKET client) {
    // 创建结果
    FindUsersResult* result = New_FindUsersResult();
    int i = 0;
    for (OnlineUser* currentUser = server->mOnlineUsers->mHead; currentUser != NULL; currentUser = currentUser->next) {
        // 跳过自己的名字
        if (_tcscmp(currentUser->username, request->mUsername) == 0) {
            continue;
        }
        _tcscpy_s(result->mUsernameList[i], USERNAME_BUF_LEN, currentUser->username);
        i++;
    }

    // 发送结果
    Server_SendData(client, result, sizeof(FindUsersResult));

    free(result);
}

/**
 * 处理登录请求。
 * @param server    服务器
 * @param request   请求
 * @param client    客户端
 */
static void Server_HandleLogon(Server* server, GomokuProtocol* request, SOCKET client) {
    GomokuProtocol* result;

    // 如果已登录，则直接返回
    BOOL isOnline = server->mOnlineUsers->find(server->mOnlineUsers, request->mUsername) != INVALID_SOCKET;
    if (isOnline) {
        result = New_GomokuProtocol(LOGON, _T(""));
        Server_SendData(client, result, sizeof(GomokuProtocol));
        free(result);
        return;
    }

    // 保存用户
    server->mOnlineUsers->setAt(server->mOnlineUsers, client, request->mUsername);

    // 创建结果
    result = New_GomokuProtocol(LOGON, request->mUsername);

    // 发送结果
    Server_SendData(client, result, sizeof(GomokuProtocol));

    free(result);
}

/**
 * 处理协议。
 * @param server    服务器
 * @param pro       请求
 */
static void Server_HandleProtocol(Server* server, GomokuProtocol* pro) {
    int dataSize = -1;

    // 如果目标用户不在线则返回
    SOCKET rivalUser = server->mOnlineUsers->find(server->mOnlineUsers, pro->mUsername);
    if (rivalUser == INVALID_SOCKET) {
        return;
    }

    // 计算数据长度
    switch (pro->mType) {
        case GAME_OVER:
            dataSize = sizeof(GameOver);
            break;
        case INVITE_REQUEST:
            dataSize = sizeof(InviteRequest);
            break;
        case INVITE_RESULT:
            dataSize = sizeof(InviteResult);
            break;
        case PUT_PIECE:
            dataSize = sizeof(PutPiece);
            break;
    }

    // 发送给目标用户
    Server_SendData(rivalUser, pro, dataSize);
}

/**
 * 处理退出游戏请求。
 * @param server    服务器
 * @param request   请求
 * @param client    客户端
 */
static void Server_HandleQuitGame(Server* server, GomokuProtocol* request, SOCKET client) {
    OnlineUser* onlineUser = NULL;

    // 广播该用户的请求
    for (onlineUser = server->mOnlineUsers->mHead; onlineUser != NULL; onlineUser = onlineUser->next) {
        Server_SendData(onlineUser->sock, request, sizeof(GomokuProtocol));
    }

    // 将该用户从在线列表移除
    Server_RemoveUser(server, client);
}

/**
 * 处理客户端发送来的数据。
 * @param server    服务器
 * @param pro       协议
 * @param client    客户端
 */
static void Server_OnGetMessageFromClient(Server* server, GomokuProtocol* pro, SOCKET client) {
    switch (pro->mType) {
        case FIND_USERS:
            Server_HandleFindUsers(server, pro, client);
            break;
        case LOGON:
            Server_HandleLogon(server, pro, client);
            break;
        case QUIT_GAME:
            Server_HandleQuitGame(server, pro, client);
            break;
        default:
            Server_HandleProtocol(server, pro);
            break;
    }
}

/**
 * 服务器线程。
 * @param param 服务器
 * @return 总是零
 */
static DWORD WINAPI Server_Thread(LPVOID param) {
    Server* server = (Server*)param;        // 服务器
    u_int i = 0;                            // 循环变量
    OnlineUser* user = NULL;                // 在线用户
    int size = 0;                           // 数据长度
    char* buffer = NULL;                    // 接收数据的缓存
    int result = 1;                         // 返回值
    SOCKET clientSocket = INVALID_SOCKET;   // 客户端套接字
    fd_set fdRead = { 0 };                  // 套接字集合

    while (TRUE) {
        // 将服务器套接字加入到 fdRead 中
        FD_ZERO(&fdRead);
        FD_SET(server->mSocket, &fdRead);

        // 将所有客户端套接字加入到 fdRead 中
        for (user = server->mOnlineUsers->mHead; user != NULL; user = user->next) {
            FD_SET(user->sock, &fdRead);
        }

        // 选择所有可读的客户端套接字
        result = select(0, &fdRead, NULL, NULL, NULL);
        if (result == SOCKET_ERROR) {
            break;
        }

        // 遍历所有套接字，找到可读的
        for (i = 0; i < fdRead.fd_count; i++) {
            if (fdRead.fd_array[i] == server->mSocket) {
                // 如果是服务器的套接字，则接受用户连接
                // 将用户套接字加入到在线用户列表中
                clientSocket = accept(server->mSocket, NULL, NULL);
                if (clientSocket == SOCKET_ERROR) {
                    return 0;
                }
                server->mOnlineUsers->addSocket(server->mOnlineUsers, clientSocket);
            } else {
                // 如果是客户端的套接字，则接收数据
                // 接收数据长度
                result = recv(fdRead.fd_array[i], (char*)&size, sizeof(size), 0);
                if (result <= 0) {
                    Server_RemoveUser(server, fdRead.fd_array[i]);
                    continue;
                }

                // 接收数据
                buffer = (char*)calloc(1, size);
                result = recv(fdRead.fd_array[i], buffer, size, 0);
                if (result <= 0) {
                    Server_RemoveUser(server, fdRead.fd_array[i]);
                    free(buffer);
                    continue;
                }

                // 将数据转换成协议
                GomokuProtocol* pro = (GomokuProtocol*)buffer;

                // 处理协议
                Server_OnGetMessageFromClient(server, pro, fdRead.fd_array[i]);

                // 释放缓存
                free(buffer);
            }
        }
    }

    return 0;
}

/**
 * 删除用户。
 * @param server    服务器
 * @param client    客户端
 */
static void Server_RemoveUser(Server* server, SOCKET client) {
    // 删除该用户
    server->mOnlineUsers->removeBySocket(server->mOnlineUsers, client);

    // 关闭客户端
    closesocket(client);
}

/**
 * 发送数据。
 * @param client    客户端
 * @param pro       协议
 * @param dataSize  数据长度
 */
static void Server_SendData(SOCKET client, const void* pro, int dataSize) {
    // 发送数据长度
    send(client, (const char*)&dataSize, sizeof(dataSize), 0);

    // 发送数据
    send(client, pro, dataSize, 0);
}

/**
 * 开启服务。
 * @param server    服务器
 * @return 成功则返回 TRUE，否则返回 FALSE
 */
static BOOL Server_Start(Server* server) {
    SOCKADDR_IN serverAddr = { AF_INET };
    HANDLE threadHandle = NULL;
    int result = 0;

    // 绑定 IP 地址和端口号
    inet_pton(AF_INET, SERVER_IP, &(serverAddr.sin_addr));
    serverAddr.sin_port = htons(SERVER_PORT);
    result = bind(server->mSocket, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR_IN));
    if (result == SOCKET_ERROR) {
        return FALSE;
    }

    // 侦听
    result = listen(server->mSocket, SOMAXCONN);
    if (result == SOCKET_ERROR) {
        return FALSE;
    }

    // 开启接收数据的线程
    threadHandle = CreateThread(NULL, 0, Server_Thread, server, 0, 0);
    return CloseHandle(threadHandle);
}
